🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / data / src / commonTest / kotlin / org / meshtastic / core / data / manager / MeshMessageProcessorImplTest.kt
Displaying Raw • Download
core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshMessageProcessorImplTest.kt 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 30.34 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager
Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.calls
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.answering.throws
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.everySuspend
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.matcher.matches
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787dev.mokkery.verify
Tff7b72import T7ee787dev.mokkery.verify.VerifyMode
Tff7b72import T7ee787dev.mokkery.verifySuspend
Tff7b72import T7ee787kotlinx.coroutines.CompletableDeferred
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.async
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.test.UnconfinedTestDispatcher
Tff7b72import T7ee787kotlinx.coroutines.test.advanceUntilIdle
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.common.di.asServiceScope
Tff7b72import T7ee787org.meshtastic.core.repository.FromRadioPacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.MeshDataHandler
Tff7b72import T7ee787org.meshtastic.core.repository.MeshLogRepository
Tff7b72import T7ee787org.meshtastic.core.repository.NodeManager
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionContext
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionLease
Tff7b72import T7ee787org.meshtastic.core.repository.ReceivedRadioFrame
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.proto.Data
Tff7b72import T7ee787org.meshtastic.proto.FromRadio
Tff7b72import T7ee787org.meshtastic.proto.LogRecord
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertTrue
Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tff7b72class T56d364MeshMessageProcessorImplTest Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3nodeManager Tff7b72= Te6edf3mockTff7b72<Te6edf3NodeManagerTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3serviceRepository Tff7b72= Te6edf3mockTff7b72<Te6edf3ServiceRepositoryTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3meshLogRepository Tff7b72= Te6edf3mockTff7b72<Te6edf3MeshLogRepositoryTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3fromRadioDispatcher Tff7b72= Te6edf3mockTff7b72<Te6edf3FromRadioPacketHandlerTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3dataHandler Tff7b72= Te6edf3mockTff7b72<Te6edf3MeshDataHandlerTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioInterfaceService Tff7b72= Te6edf3mockTff7b72<Te6edf3RadioInterfaceServiceTff7b72>Tb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3testDispatcher Tff7b72= Te6edf3UnconfinedTestDispatcherTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3processorTb4b4b4: Te6edf3MeshMessageProcessorImpl
Tff7b72private Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4T79c0ff5
Tff7b72private Tff7b72val Te6edf3session Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff3LTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6fftcp:testTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3activeSession Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3RadioSessionContext?Tff7b72>Tb4b4b4(Te6edf3sessionTb4b4b4)
Tff7b72private Tff7b72val Te6edf3isNodeDbReady Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Tff7b72falseTb4b4b4)
Tff7b72private Tff7b72fun Td2a8fftestLeaseTb4b4b4(Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContextTb4b4b4)Tb4b4b4: Te6edf3RadioSessionLease Tff7b72= Tff7b72object Tb4b4b4: T56d364RadioSessionLease Tb4b4b4{
Tff7b72override Tff7b72val Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext Tff7b72= Te6edf3session
Tff7b72override Tff7b72fun Td2a8ffisCurrentTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3session
Tb4b4b4}
Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetUpTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72= Te6edf3session
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3isNodeDbReady Tb4b4b4} Te6edf3returns Te6edf3isNodeDbReady
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTff7b72<Tffa657Int?Tff7b72>Tb4b4b4(Te6edf3myNodeNumTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3activeSession Tb4b4b4} Te6edf3returns Te6edf3activeSession
Te6edf3every Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3isSessionActiveTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Tb4b4b4(Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3RadioSessionContextTb4b4b4)
Tb4b4b4}
Te6edf3every Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runIfSessionActiveTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3requestedSession Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3RadioSessionContext
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNCHECKED_CASTTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3block Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff1Tff7b72] Tff7b72as Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657Unit
Tff7b72if Tb4b4b4(Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3requestedSessionTb4b4b4) Tb4b4b4{
Te6edf3blockTb4b4b4(Tb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72false
Tb4b4b4}
Tb4b4b4}
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWhileSessionActiveTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3requestedSession Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3RadioSessionContext
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNCHECKED_CASTTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3block Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff1Tff7b72] Tff7b72as Tb4b4b4(Te6edf3suspend Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3requestedSessionTb4b4b4) Tb4b4b4{
Te6edf3blockTb4b4b4(Tb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72false
Tb4b4b4}
Tb4b4b4}
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3requestedSession Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3RadioSessionContext
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNCHECKED_CASTTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3block Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff1Tff7b72] Tff7b72as Tb4b4b4(Te6edf3suspend Tb4b4b4(Te6edf3RadioSessionLeaseTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72=Tff7b72= Te6edf3requestedSessionTb4b4b4) Tb4b4b4{
Te6edf3blockTb4b4b4(Te6edf3testLeaseTb4b4b4(Te6edf3requestedSessionTb4b4b4)Tb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72false
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffcreateProcessorTb4b4b4(Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4)Tb4b4b4: Te6edf3MeshMessageProcessorImpl Tff7b72= Te6edf3MeshMessageProcessorImplTb4b4b4(
Te6edf3nodeManager Tff7b72= Te6edf3nodeManagerTb4b4b4,
Te6edf3serviceStateWriter Tff7b72= Te6edf3serviceRepositoryTb4b4b4,
Te6edf3meshLogRepository Tff7b72= Te6edf3lazy Tb4b4b4{ Te6edf3meshLogRepository Tb4b4b4}Tb4b4b4,
Te6edf3dataHandler Tff7b72= Te6edf3lazy Tb4b4b4{ Te6edf3dataHandler Tb4b4b4}Tb4b4b4,
Te6edf3fromRadioDispatcher Tff7b72= Te6edf3fromRadioDispatcherTb4b4b4,
Te6edf3radioInterfaceService Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4,
Te6edf3scope Tff7b72= Te6edf3scopeTb4b4b4.Te6edf3asServiceScopeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffframeTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3frameSessionTb4b4b4: Te6edf3RadioSessionContext Tff7b72= Te6edf3sessionTb4b4b4) Tff7b72=
Te6edf3ReceivedRadioFrameTb4b4b4(Te6edf3bytesTb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3frameSessionTb4b4b4)
Tff7b72private Tff7b72suspend Tff7b72fun Te6edf3MeshMessageProcessorImplTb4b4b4.Td2a8ffhandleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4, Te6edf3myNodeNumTb4b4b4: Tffa657Int?Tb4b4b4) Tb4b4b4{
Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3sessionTb4b4b4)
Tb4b4b4}
T8b949e// ---------- handleFromRadio: non-packet variants ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`handleFromRadio dispatches non-packet variants to fromRadioDispatcher`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3logRecord Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6fftest logTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3logRecordTb4b4b4)
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3bytesTb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verify Tb4b4b4{ Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleFromRadio falls back to LogRecord parsing when FromRadio fails`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
T8b949e// Encode a raw LogRecord (not wrapped in FromRadio) — first decode as FromRadio fails,
T8b949e// fallback decode as LogRecord succeeds
Tff7b72val Te6edf3logRecord Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6fffallback logTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3logRecordTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3bytesTb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Should have been dispatched as a FromRadio with log_record set
Te6edf3verify Tb4b4b4{ Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`handleFromRadio with completely invalid bytes does not crash`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
T8b949e// Invalid protobuf bytes — both parses should fail
Tff7b72val Te6edf3garbage Tff7b72= Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3xFFTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff0Te6edf3xFETb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff0Te6edf3xFDTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3garbageTb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// No crash
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`stale session frame is dropped before dispatch or persistence`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff4LTb4b4b4, Te6edf3address Tff7b72= Te6edf3sessionTb4b4b4.Te6edf3addressTb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffstaleTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifyTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`session revoked after decode cannot reach packet handlers`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffrevokedTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWhileSessionActiveTb4b4b4(Te6edf3sessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifyTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`packet processing awaits node persistence before releasing session authority`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72var Te6edf3authorityDepth Tff7b72= T79c0ff0
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWhileSessionActiveTb4b4b4(Te6edf3sessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNCHECKED_CASTTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3block Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff1Tff7b72] Tff7b72as Tb4b4b4(Te6edf3suspend Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)
Te6edf3authorityDepth Tff7b72+Tff7b72= T79c0ff1
Tff7b72try Tb4b4b4{
Te6edf3blockTb4b4b4(Tb4b4b4)
Tff7b72true
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3authorityDepth Tff7b72-Tff7b72= T79c0ff1
Tb4b4b4}
Tb4b4b4}
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3persistenceStarted Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3releasePersistence Tff7b72= Te6edf3CompletableDeferredTff7b72<Tffa657UnitTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3assertTrueTb4b4b4(Te6edf3authorityDepth Tff7b72> T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6ffnode persistence must begin while session authority is heldTa5d6ff"Tb4b4b4)
Te6edf3persistenceStartedTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3releasePersistenceTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff9Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3processing Tff7b72= Te6edf3async Tb4b4b4{ Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3packetTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4) Tb4b4b4}
Te6edf3persistenceStartedTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3processingTb4b4b4.Te6edf3isCompletedTb4b4b4, Ta5d6ff"Ta5d6ffsession authority must remain held until node persistence finishesTa5d6ff"Tb4b4b4)
Te6edf3releasePersistenceTb4b4b4.Te6edf3completeTb4b4b4(Tffa657UnitTb4b4b4)
Te6edf3processingTb4b4b4.Te6edf3awaitTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`local sender packet persists one combined node update`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3myNodeNumTb4b4b4)
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3packetTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`replacement generation refreshes the local node without waiting for the prior throttle`Tb4b4b4(Tb4b4b4) Tff7b72=
Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffaliveTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3replacementSession Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff4LTb4b4b4, Te6edf3address Tff7b72= Te6edf3sessionTb4b4b4.Te6edf3addressTb4b4b4)
Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72= Te6edf3replacementSession
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3replacementSessionTb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`a throwing handler does not propagate out of handleFromRadio`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
T8b949e// A malformed-but-decodable packet can make a downstream handler throw. That must NOT escape to the
T8b949e// receivedData collector, which would cancel the collection and silently deafen the radio (a remote DoS).
T8b949e// Regression guard for the safeCatching in processFromRadio: before that fix this call rethrew and failed.
Te6edf3every Tb4b4b4{ Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3throws Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6ffhostile packetTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffboomTa5d6ff"Tb4b4b4)Tb4b4b4) T8b949e// routes to fromRadioDispatcher
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4) T8b949e// must return normally, not throw
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verify Tb4b4b4{
Te6edf3fromRadioDispatcherTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4)
Tb4b4b4} T8b949e// handler ran and threw, yet we are still here
Tb4b4b4}
T8b949e// ---------- handleReceivedMeshPacket: early buffering ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`packets are buffered when node DB is not ready`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Packet should be buffered, not processed
T8b949e// (no emitMeshPacket call since DB is not ready)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`buffered packets are flushed when node DB becomes ready`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Now make DB ready
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Buffered packet should have been flushed and processed
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`buffer replay pauses without losing order when node DB readiness regresses`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tff7b72val Te6edf3handledIds Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657IntTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataHandlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3MeshPacket
Te6edf3handledIds Tff7b72+Tff7b72= Te6edf3packetTb4b4b4.Te6edf3id
Tff7b72if Tb4b4b4(Te6edf3packetTb4b4b4.Te6edf3id Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tb4b4b4}
Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3id Tff7b72-Tff7b72>
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3idTb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0 Tff7b72+ Te6edf3idTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3myNodeNumTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4)Tb4b4b4, Te6edf3handledIdsTb4b4b4)
T8b949e// Handling packet 1 regressed readiness to false, so this true transition rearms the replay collector.
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4)Tb4b4b4, Te6edf3handledIdsTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`packet audit work is rejected when session lease admission closes`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3sessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff6Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3packetTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`buffered packet from retired session is not replayed into replacement session`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tff7b72val Te6edf3processedIds Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657IntTff7b72>Tb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataHandlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Te6edf3processedIds Tff7b72+Tff7b72= Tb4b4b4(Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3MeshPacketTb4b4b4)Tb4b4b4.Te6edf3id
Tb4b4b4}
Tff7b72val Te6edf3oldPacket Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff7Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3oldPacketTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3replacementSession Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff4LTb4b4b4, Te6edf3address Tff7b72= Te6edf3sessionTb4b4b4.Te6edf3addressTb4b4b4)
Te6edf3activeSessionTb4b4b4.Te6edf3value Tff7b72= Te6edf3replacementSession
Tff7b72val Te6edf3replacementPacket Tff7b72= Te6edf3oldPacketTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= T79c0ff8Tb4b4b4, Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(
Te6edf3frameTb4b4b4(Te6edf3FromRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3replacementPacketTb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3replacementSessionTb4b4b4)Tb4b4b4,
Te6edf3myNodeNumTb4b4b4,
Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(T79c0ff8Tb4b4b4)Tb4b4b4, Te6edf3processedIdsTb4b4b4)
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`early buffer overflow drops oldest packet`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
T8b949e// The maxEarlyPacketBuffer is 10240 — we won't actually fill it in this test,
T8b949e// but we test the boundary behavior conceptually. Instead, test that multiple
T8b949e// packets are accumulated properly.
Te6edf3repeatTb4b4b4(T79c0ff5Tb4b4b4) Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3iTb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0 Tff7b72+ Te6edf3iTb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Tb4b4b4}
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Flush
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// All 5 packets should have been processed
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- flushEarlyReceivedPackets: handler resilience ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`a throwing dataHandler for one buffered packet does not stop the flush or crash`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
T8b949e// Regression guard for a crash found via adversarial mesh-fuzz testing (corrupted/garbage NodeInfo
T8b949e// packets injected into a live replay session): a malformed packet buffered before the node DB /
T8b949e// myNodeNum were ready, then replayed by flushEarlyReceivedPackets, threw an uncaught
T8b949e// ProtocolException/EOFException from deep inside handleReceivedData -> handleNodeInfo's proto
T8b949e// decode. Unlike the live path (guarded by safeCatching in processFromRadio), that exception
T8b949e// escaped the bare `scope.launch` in flushEarlyReceivedPackets and crashed the whole app process
T8b949e// (observed live: "FATAL EXCEPTION" at MeshMessageProcessorImpl.kt:214, PID killed, "keeps
T8b949e// stopping" dialog on device). Fixed by wrapping each buffered packet's processing in safeCatching.
Te6edf3every Tb4b4b4{ Te6edf3dataHandlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3throws
Te6edf3RuntimeExceptionTb4b4b4(Ta5d6ff"Ta5d6ffcorrupted NodeInfoTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3poisoned Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODEINFO_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3healthy Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff8Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff1Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3poisonedTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3healthyTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4) T8b949e// must not throw -- this is the crash repro point
T8b949e// Both packets were attempted -- the poisoned packet's throw did not stop the rest of the batch.
Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(T79c0ff9T79c0ff9T79c0ff8Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- handleReceivedMeshPacket: rx_time normalization ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`packets with rx_time 0 get current time`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff0Tb4b4b4, T8b949e// should be replaced with current time
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`packets with non-zero rx_time keep their time`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`packets with absent rx_time get current time`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff3Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= Tff7b72nullTb4b4b4, T8b949e// radio had no clock at reception
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{
Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3matchesTff7b72<Te6edf3MeshPacketTff7b72> Tb4b4b4{ Te6edf3emitted Tff7b72-Tff7b72> Tb4b4b4(Te6edf3emittedTb4b4b4.Te6edf3rx_time Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72> T79c0ff0 Tb4b4b4}Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- handleReceivedMeshPacket: node updates ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`processReceivedMeshPacket updates myNode lastHeard`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Should have called updateNode for myNodeNum (lastHeard update)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`processReceivedMeshPacket updates sender node`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3senderNode Tff7b72= T79c0ff9T79c0ff9T79c0ff9
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3from Tff7b72= Te6edf3senderNodeTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3channel Tff7b72= T79c0ff1Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Should have called updateNode for the sender
Te6edf3verifySuspend Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3updateNodeAndPersistTb4b4b4(Te6edf3senderNodeTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- handleReceivedMeshPacket: null decoded ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`packet with null decoded is skipped`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3id Tff7b72= T79c0ff1Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3decoded Tff7b72= Tff7b72nullTb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// No crash, no emitMeshPacket call (decoded is null so processReceivedMeshPacket returns early)
Tb4b4b4}
T8b949e// ---------- handleReceivedMeshPacket: myNodeNum not yet known ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`packets received before myNodeNum is known are buffered until it resolves`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
T8b949e// Our own node number is not yet known, even though the node DB is ready.
Tff7b72val Te6edf3myNodeNumFlow Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657Int?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3myNodeNumFlow
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Tff7b72nullTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Buffered, not processed: storing now could key a local packet under its raw from_num and orphan it from
T8b949e// per-node chart queries. Neither the DB insert nor the downstream emit should have happened yet.
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Once myNodeNum resolves, the buffer flushes and the packet is processed (stored + emitted).
Te6edf3myNodeNumFlowTb4b4b4.Te6edf3value Tff7b72= T79c0ff4T79c0ff3T79c0ff2T79c0ff1
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`buffer survives a reconnect toggle and flushes only once myNodeNum resolves`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNumFlow Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657Int?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3myNodeNumFlow
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true T8b949e// DB ready, our node number still unknown
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Tff7b72nullTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Reconnect: isNodeDbReady toggles false -> true while myNodeNum stays null. The packet must stay buffered
T8b949e// —
T8b949e// flushing now would key it under its raw from_num.
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
T8b949e// Only once myNodeNum finally resolves does the buffer flush.
Te6edf3myNodeNumFlowTb4b4b4.Te6edf3value Tff7b72= T79c0ff4T79c0ff3T79c0ff2T79c0ff1
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- clearEarlyPackets ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`clearEarlyPackets empties the buffer`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72false
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3rx_time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleReceivedMeshPacketTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3clearEarlyPacketsTb4b4b4(Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
T8b949e// Now make DB ready — the buffer should be empty, nothing to flush
Te6edf3isNodeDbReadyTb4b4b4.Te6edf3value Tff7b72= Tff7b72true
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3VerifyModeTb4b4b4.Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3emitMeshPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e// ---------- logVariant ----------
Tf0883e@Test
Tff7b72fun Td2a8ff`FromRadio log_record variant is logged as MeshLog`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Te6edf3processor Tff7b72= Te6edf3createProcessorTb4b4b4(Te6edf3backgroundScopeTb4b4b4)
Tff7b72val Te6edf3logRecord Tff7b72= Te6edf3LogRecordTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffdevice logTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3FromRadioTb4b4b4(Te6edf3log_record Tff7b72= Te6edf3logRecordTb4b4b4)
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3fromRadioTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Te6edf3processorTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3frameTb4b4b4(Te6edf3bytesTb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.23s